/*
 *    Copyright (c) 2006 ThoughtWorks, Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.thoughtworks.turtlemock.internal;

import com.thoughtworks.turtlemock.constraint.Constraint;
import com.thoughtworks.turtlemock.fluapi.Should;
import junit.framework.Assert;

public class BaseMock {

    private static interface Action {
        Action NULL = new Action() {
            public void notify(String invokingOperationName) {
            }
        };

        void notify(String invokingOperationName);
    }

    public static final int TIMEOUT = 1000;

    private InvokeResultLogs invokeResultLogs = new InvokeResultLogs();
    private Behaviour behaviour = new Behaviour();
    private Action waitingForOperation = Action.NULL;

    public ReturnValue ifCall(String operationName) {
        ReturnValue returnValue = new ReturnValue(operationName);
        behaviour.add(returnValue);
        return returnValue;
    }

    public InvokeMethodResultLog assertDid(String operationName) {
        InvokeMethodResultLog invokeMethodResultLog = invokeResultLogs.getByOperationName(operationName);
        String message = "operation " + operationName + " should be invoked, but not";
        Assert.assertTrue(message, invokeMethodResultLog != null && invokeMethodResultLog.timesNotAsserted() > 0);
        return new OnceAssertedInvokeMethodResultLog(invokeMethodResultLog);
    }

    public void assertNotDid(String operationName) {
        assertNotDid(operationName, Should.beAny());
    }

    public void assertDidNothing() {
        Assert.assertTrue("No operation should be invoked, but did", invokeResultLogs.isEmpty());
        ;
    }

    public void assertNotDid(String operationName, Constraint allArgsContraint) {
        InvokeMethodResultLog invokeMethodResultLog = invokeResultLogs.getByOperationName(operationName);
        if (invokeMethodResultLog != null) {
            invokeMethodResultLog.withAllArgsConstraint(allArgsContraint).times(0);
        }
    }

    public InvokeMethodResultLog assertDidInAnotherThread(String operationName, int timeout) throws InterruptedException {
        waitTillInvoke(operationName, timeout);
        return assertDid(operationName).inAnotherThread();
    }

    public InvokeMethodResultLog assertDidInAnotherThread(String operationName) throws InterruptedException {
        return assertDidInAnotherThread(operationName, TIMEOUT);
    }

    private void waitTillInvoke(final String operationName, long timeout) throws InterruptedException {
        synchronized (invokeResultLogs) {
            InvokeMethodResultLog invokeMethodResultLog = invokeResultLogs.getByOperationName(operationName);
            if (invokeMethodResultLog == null || invokeMethodResultLog.timesNotAsserted() == 0) {
                waitingForOperation = new Action() {
                    public void notify(String invokingOperationName) {
                        if (operationName.equals(invokingOperationName)) {
                            invokeResultLogs.notify();
                        }
                    }
                };
                invokeResultLogs.wait(timeout);
            } else {
                waitingForOperation = Action.NULL;
            }
        }
    }

    public Object mockingOperation(String operationName, Object[] params) throws ExpectedException {
        synchronized (invokeResultLogs) {
            invokeResultLogs.add(new OnceInvokeMethodResultLog(operationName, params));
            waitingForOperation.notify(operationName);
        }
        return behaviour.getReturnValue(operationName, params);
    }
}
